package org.wheel.example;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.wheel.example.bean.clazz.ClazzModel;
import org.wheel.example.bean.clazz.TableFieldExtend;
import org.wheel.plugins.starter.jdbc.pool.dao.IBaseDao;
import org.wheel.plugins.starter.jdbc.pool.service.BaseService;
import org.wheel.plugins.starter.jdbc.pool.service.impl.BaseServiceImpl;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

public class OriginCodeGenerator {

//    private static String JDBC_URL = "jdbc:mysql://localhost:3306/szrq?useSSL=false&requireSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useServerPrepStmts=true&cachePrepStmts=true";
//    private static String JDBC_USER = "root";
//    private static String JDBC_PASSWD = "123456";

    private static String JDBC_URL = "jdbc:mysql://localhost:3308/wx_course_manage?useSSL=false&requireSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useServerPrepStmts=true&cachePrepStmts=true";
    private static String JDBC_USER = "root";
    private static String JDBC_PASSWD = "P@ssw0rdMysql";

//    private static String JDBC_URL = "jdbc:postgresql://10.1.1.64:5432/mews_tw?currentSchema=public";
//    private static String JDBC_USER = "mews";
//    private static String JDBC_PASSWD = "pg@mews";

//    private static String JDBC_URL = "jdbc:postgresql://10.9.6.29:11347/postgres?currentSchema=over_limit";
//    private static String JDBC_USER = "szrq";
//    private static String JDBC_PASSWD = "szrq@123";

    private static final DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig
            .Builder(JDBC_URL, JDBC_USER, JDBC_PASSWD);

    private static String PROJECT_ROOT_PATH = "/source-code/";
    private static String ROOT_PACKAGE = "org.wheel";
    private static final String DEFAULT_MOUDLE_NAME = "";
    // 生成的代码根目录
    private static String PROJECT_FOLDER_PATH = System.getProperty("user.home");
    private static String SYSTEM_AUTHOR_NAME = System.getProperty("user.name");

    /**
     * UpdateFieldStrategy
     */
    private final static List<String> excludeFilds = Lists.newArrayList();
    /**
     * 继承父类后, 基类中需要过滤掉的公共字段
     */
//        private final static List<String> commonFields = Lists.newArrayList("id", "del_flag", "revision", "id_create", "gmt_create", "id_modified", "gmt_modified");
    private final static List<String> commonFields = Lists.newArrayList();
    /**
     * 逻辑删除字段
     */
    private final static String logicDelFiled = "del_flag";
    private final static List<String> dtoExcludeImportPackages = Lists.newArrayList(TableName.class.getCanonicalName(), TableId.class.getCanonicalName(), TableField.class.getCanonicalName(), Serializable.class.getCanonicalName());
//    private final static List<String> dtoExcludeImportPackages = Lists.newArrayList(Table.class.getCanonicalName(), Id.class.getCanonicalName(), Column.class.getCanonicalName(), Serializable.class.getCanonicalName());
    /**
     * src/main/java
     */
    private final static String JAVA_CODE_FOlDER_PATH = PROJECT_ROOT_PATH + "src/main/java";
    /**
     * src/main/resources
     */
    private final static String RESOURCE_FOlDER_PATH = PROJECT_ROOT_PATH + "src/main/resources";
    /**
     * src/main/vue
     */
    private final static String VUE_FOlDER_PATH = PROJECT_ROOT_PATH + "vue";
    /**
     * src/main/resources/mapper
     */
    private final static String MAPPER_XML_FOLDER_PATH = RESOURCE_FOlDER_PATH + "/mapper";


    /**
     * <pre>
     * /templates/one/  jdk8对应的模板
     * /templates/jdk17/ jdk17对应的模板
     * </pre>
     */
    private final static String TEMPLATE_PATH = "/templates/origin";

    private final static String VUE_TEMPLATE_ROOT_PATH = "/templates/leaf6-odin";

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    @SuppressWarnings("resource")
    public static String scanner(String tip, boolean allowBlank, String defaultValue) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + "：");

        // 如果 allowBlank == true，允许输入回车，则返回默认值，否则不允许输入回车，继续等待用户输入
        if (allowBlank) {
            System.out.print(help.toString());
            String ipt = scanner.nextLine();
            if(StringUtils.isBlank(ipt)){
                ipt = defaultValue;
                System.out.println(ipt);
            }
            return ipt;
        }else{
            System.out.println(help.toString());
        }
        while (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return StringUtils.deleteWhitespace(ipt);
            }else {
                if(allowBlank) {
                    return defaultValue;
                }
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "！");
    }


    public static void main(String[] args) {
        String parentPackageName = scanner("主包路径[默认路径："+ROOT_PACKAGE+"]", true, ROOT_PACKAGE);
        String moduleName = scanner("模块名[默认模块名称："+DEFAULT_MOUDLE_NAME+", 多级模块输入格式：business.demo]", true, DEFAULT_MOUDLE_NAME);
//        System.out.println(Joiner.on("/").join(moduleName.split("\\.")));
        FastAutoGenerator fag = FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                .globalConfig(builder -> {
                    builder.author(SYSTEM_AUTHOR_NAME) // 设置作者
//                            .enableSpringdoc()
                            .disableOpenDir() //禁止打开输出目录
                            .dateType(DateType.TIME_PACK) // 时间策略
                            .outputDir(PROJECT_FOLDER_PATH+JAVA_CODE_FOlDER_PATH); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent(parentPackageName) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
//                            .entity("entity") // 设置 Entity 包名
//                            .service("blo") // 设置 Service 包名
//                            .serviceImpl("blo.impl") // 设置 Service Impl 包名
                            .mapper("dao") // 设置 Mapper 包名
//                            .xml("mappers") // 设置 Mapper XML 包名
//                            .controller("controller") // 设置 Controller 包名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, PROJECT_FOLDER_PATH+MAPPER_XML_FOLDER_PATH+"/"+moduleName.replaceAll("\\.", "/"))) // 设置mapperXml生成路径
                    ;
                })
                .strategyConfig((scanner, builder) -> builder
                        .addTablePrefix(moduleName) // 过滤前缀
                        .addInclude(getTables(scanner.apply("请输入表名，多个英文逗号分隔，所有输入 all")))

                        .controllerBuilder()
                        .template(Joiner.on("/").join(TEMPLATE_PATH, "controller.java"))
                        .enableFileOverride().enableRestStyle().enableHyphenStyle()
//                        .superClass(BaseController.class)


                        .serviceBuilder()
////                        .disableService()  // 禁用 Service 层生成
////                        .disableServiceImpl()  // 禁用 ServiceImpl 层生成
                        .formatServiceFileName("I%sService")
                        .formatServiceImplFileName("%sServiceImpl")
//                        .serviceTemplate(Joiner.on("/").join(TEMPLATE_PATH, "service.java"))
//                        .serviceImplTemplate(Joiner.on("/").join(TEMPLATE_PATH, "serviceImpl.java"))
                        .enableFileOverride().superServiceClass(BaseService.class).superServiceImplClass(BaseServiceImpl.class)


                        .mapperBuilder()
                        .mapperTemplate(Joiner.on("/").join(TEMPLATE_PATH, "mapper.java"))
                        .disableMapperXml()
//                        .mapperXmlTemplate(Joiner.on("/").join(TEMPLATE_PATH, "mapper.xml"))
                        .enableFileOverride().enableBaseResultMap().enableBaseColumnList().superClass(IBaseDao.class)
                        .convertMapperFileName(name->name+"Dao")  // 自定义Mapper类名

                        .entityBuilder()
                        .enableLombok()
                        .enableSerialAnnotation()
                        .javaTemplate(Joiner.on("/").join(TEMPLATE_PATH, "entity.java"))
                        .enableFileOverride().enableLombok().enableRemoveIsPrefix()//.logicDeleteColumnName(logicDelFiled)

                        .build())
                .injectionConfig(consumer -> {
                    // 自定义模板
                    PackageConfig.Builder packageBuilder = new PackageConfig.Builder()
                            .parent(parentPackageName)
                            .moduleName(moduleName)
                            ;

                    consumer.beforeOutputFile((tableInfo, objectMap) -> {
//                        System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
                                // 实体策略, 设置entity类上显示 @Table 注解
                                tableInfo.setConvert(true);
                                // 增加自定义属性，用户输入的module名称，如果不为空，则进行处理，最终放置在Controller的Mapping属性中
                                Set<String> customModuleNameSet = tableInfo.getStrategyConfig().getTablePrefix();
                                if(!customModuleNameSet.isEmpty()){
                                    String customModuleName = customModuleNameSet.stream().filter(StrUtil::isNotBlank).findFirst().orElse(null);
                                    if(StrUtil.isNotBlank(customModuleName)) {
                                        String[] split = customModuleName.split("\\.");
                                        objectMap.put("customModuleName", Joiner.on("/").join(Arrays.stream(split).map(String::toLowerCase).toArray()));
                                    }
                                }else{
                                    objectMap.put("customModuleName", "");
                                }

                                // 字段集合数据转换
                                List<TableFieldExtend> allFilds = tableInfo.getFields().stream().map(o-> TableFieldExtend.builder().field(o).existFieldStrategy(false).build()).collect(Collectors.toList());
                                // 公共数据
                                List<TableFieldExtend> boFilds=allFilds.stream()
                                        .filter(filed->!commonFields.contains(filed.getField().getName().toLowerCase()))
                                        .collect(Collectors.toList());
                                Set<String> importPackages = tableInfo.getImportPackages().stream().filter(p->!dtoExcludeImportPackages.contains(p)).collect(Collectors.toSet());

                                // PO 所有PO类非字符串字段生成的时候，需要添加FieldStrategy更新策略
                                ClazzModel poModel = new ClazzModel();
//                                allFilds.stream().filter(filed->!excludeFilds.contains(filed.getField().getName().toLowerCase()))
//                                        .filter(o->!"String".equalsIgnoreCase(o.getField().getColumnType().getType()))
//                                        .forEach(o->{
//                                            o.setExistFieldStrategy(true);
//                                            o.setUpdateFieldStrategy(FieldStrategy.ALWAYS);
//                                        });
                                poModel.setFields(allFilds);
                                poModel.setExistsTableId(tableInfo.getFields().stream().anyMatch(o->o.isConvert() || StrUtil.isNotBlank(o.getFill())));
                                poModel.setExistsFieldStrategy(allFilds.stream().anyMatch(o->!"String".equalsIgnoreCase(o.getField().getColumnType().getType())));
                                // DTO
                                ClazzModel dtoModel = new ClazzModel();
                                dtoModel.setClazzName(tableInfo.getEntityName() + "DTO");
                                dtoModel.setClazzPackage(packageBuilder.build().joinPackage("dto"));
                                dtoModel.setImportPackages(importPackages);
                                dtoModel.setFields(boFilds);

                                // service
                                ClazzModel serviceModel = new ClazzModel();
                                serviceModel.setClazzName("I"+tableInfo.getEntityName() + ConstVal.SERVICE);
                                serviceModel.setClazzPackage(packageBuilder.build().joinPackage(packageBuilder.build().getService()));
                                serviceModel.setSuperClazzName(BaseService.class.getSimpleName());
                                serviceModel.setSuperClazzPackage(BaseService.class.getName());

                                // serviceImpl
                                ClazzModel serviceImplModel = new ClazzModel();
                                serviceImplModel.setClazzName(tableInfo.getEntityName() + ConstVal.SERVICE_IMPL);
                                serviceImplModel.setClazzPackage(packageBuilder.build().joinPackage(packageBuilder.build().getServiceImpl()));
                                serviceImplModel.setSuperClazzName(BaseServiceImpl.class.getSimpleName());
                                serviceImplModel.setSuperClazzPackage(BaseServiceImpl.class.getName());

                                objectMap.put("entityPO", poModel);
                                objectMap.put("entityDTO", dtoModel);
                                objectMap.put("serviceBPO", serviceModel);
                                objectMap.put("serviceBPOImpl", serviceImplModel);

//                                System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size()+", tableInfo JSON: "+ JSONUtil.toJsonStr(tableInfo)+", objectMap JSON: "+ JSONUtil.toJsonStr(objectMap));
                            })
                            // DTO
                            .customFile(new CustomFile.Builder()
                                    .fileName("DTO.java") // 文件后缀添加的名称
                                    .filePath(PROJECT_FOLDER_PATH+JAVA_CODE_FOlDER_PATH)
                                    .enableFileOverride()
                                    .templatePath(Joiner.on("/").join(TEMPLATE_PATH, "dto.java.ftl"))
                                    .formatNameFunction(tableInfo->tableInfo.getEntityName())
//                                    .packageName(packageBuilder.build().joinPackage( StringPool.DOT+"dto")).build())
                                    .packageName(packageBuilder.build().joinPackage( "dto"))
                                    .build())
                            // 前端VUE模板
//                            .customFile(new CustomFile.Builder()
//                                    .fileName(".route.js") // 文件后缀添加的名称
//                                    .filePath(PROJECT_FOLDER_PATH + VUE_FOlDER_PATH + "/" + packageBuilder.build().getModuleName().replaceAll("\\.", "/"))
//                                    .enableFileOverride()
//                                    .templatePath(VUE_TEMPLATE_ROOT_PATH+"/route.js.ftl")
//                                    .formatNameFunction(tableInfo->tableInfo.getEntityName().toLowerCase() +"/"+tableInfo.getEntityName())
//                                    .build())
//                            .customFile(new CustomFile.Builder()
//                                    .fileName(".vue")
//                                    .filePath(PROJECT_FOLDER_PATH + VUE_FOlDER_PATH + "/" + packageBuilder.build().getModuleName().replaceAll("\\.", "/"))
//                                    .enableFileOverride()
//                                    .templatePath(VUE_TEMPLATE_ROOT_PATH+"/page.vue.ftl")
//                                    .formatNameFunction(tableInfo->tableInfo.getEntityName().toLowerCase() +"/vue/"+ tableInfo.getEntityName())
//                                    .build())
//                            .customFile(new CustomFile.Builder()
//                                    .fileName("JS.js")
//                                    .filePath(PROJECT_FOLDER_PATH + VUE_FOlDER_PATH + "/" + packageBuilder.build().getModuleName().replaceAll("\\.", "/"))
//                                    .enableFileOverride()
//                                    .templatePath(VUE_TEMPLATE_ROOT_PATH+"/entity.js.ftl")
//                                    .formatNameFunction(tableInfo->tableInfo.getEntityName().toLowerCase() +"/js/"+ tableInfo.getEntityName())
//                                    .build())
//                            .customFile(new CustomFile.Builder()
//                                    .fileName("MixinJS.js")
//                                    .filePath(PROJECT_FOLDER_PATH + VUE_FOlDER_PATH + "/" + packageBuilder.build().getModuleName().replaceAll("\\.", "/"))
//                                    .enableFileOverride()
//                                    .templatePath(VUE_TEMPLATE_ROOT_PATH+"/entityMixin.js.ftl")
//                                    .formatNameFunction(tableInfo->tableInfo.getEntityName().toLowerCase() +"/js/"+ tableInfo.getEntityName())
//                                    .build())
                    ;
                })
                .templateEngine(new FreemarkerTemplateEngine())
                ;
        fag.execute();
    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equalsIgnoreCase(tables) ? Collections.emptyList() :
                Arrays.stream(tables.split(",")).map(String::trim).toList();
    }
}
